﻿using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Packets;

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;

namespace MqttClientNetLib {

    public class MqQosLevel {
        public static MQTTnet.Protocol.MqttQualityOfServiceLevel AtMostOnce { get; } = MQTTnet.Protocol.MqttQualityOfServiceLevel.AtMostOnce;
        public static MQTTnet.Protocol.MqttQualityOfServiceLevel AtLeastOnce { get; } = MQTTnet.Protocol.MqttQualityOfServiceLevel.AtLeastOnce;
        public static MQTTnet.Protocol.MqttQualityOfServiceLevel ExactlyOnce { get; } = MQTTnet.Protocol.MqttQualityOfServiceLevel.ExactlyOnce;


    }

    public class MqClientSubscribeItem {
        public string Topic { get; set; } = string.Empty;
        /// <summary>
        /// AtMostOnce=0;
        /// AtLeastOnce=1;
        /// ExactlyOnce=2
        /// </summary>
        public MQTTnet.Protocol.MqttQualityOfServiceLevel Level { get; set; } = MQTTnet.Protocol.MqttQualityOfServiceLevel.AtMostOnce;
        /// <summary>
        /// 该值指示发送方是否将接收其自己发布的内容
        /// </summary>
        public bool NoLocal { get; set; } = false;
        public bool RetainAsPublished { get; set; } = false;
    }

    public class MqReceiveCallbackItem {
        public string Topic { get; set; }
        public string Payload { get; set; }
    }

    public class MqPublishMessageItem {
        /// <summary>
        /// AtMostOnce=0;
        /// AtLeastOnce=1;
        /// ExactlyOnce=2
        /// </summary>
        public MQTTnet.Protocol.MqttQualityOfServiceLevel Level { get; set; } = 0;
        public bool Retain { get; set; } = false;
    }

    public class MqClientOptionItem {
        public string Ip { get; set; }
        public int Port { get; set; }
        public string ClientId { get; set; }
        public string Username { get; set; }
        public string Password { get; set; }
        public bool CleanSession { get; set; } = true;
        public MQTTnet.Formatter.MqttProtocolVersion ProtocolVersion { get; set; } = MQTTnet.Formatter.MqttProtocolVersion.V311;
    }

    public static class MqttClientFactory {
        public static IMqttClientNet Create() {
            return new MqttClientNet();
        }
    }


    public interface IMqttClientNet {
        bool IsConnect { get; }
        void ClientOption(MqClientOptionItem optionItem);
        void SubscribeOption(params MqClientSubscribeItem[] items);
        void StartAsync(int seconds = 10);
        Task PublishStringAsync(string topic, string payload);
        Task PublishStringAsync(string topic, string payload, MqPublishMessageItem item);
        void ReceiveCallback(Action<MqReceiveCallbackItem> reveive);
        void OnConnectSuccess(Action mCon);
        void OnConnectFailed(Action mFailed);
    }

    internal class MqttClientNet : IMqttClientNet {
        private MqttFactory mMqttFactory;
        private IMqttClient mMqttClient;
        private MqttClientOptions mMqttClientOptions;
        private MqttClientSubscribeOptions mMqttSubscribeOptions;
        private MqClientSubscribeItem[] mMqClientSubscribeItems;
        private List<MqttTopicFilter> mMqttTopicFilters;

        public MqttClientNet() {
            mMqttSubscribeOptions = new MqttClientSubscribeOptions();
            mMqttTopicFilters = new List<MqttTopicFilter>();
            mMqttFactory = new MqttFactory();
            mMqttClient = mMqttFactory.CreateMqttClient();
        }

        /// <summary>
        /// 是否连接服务器
        /// </summary>
        public bool IsConnect => mMqttClient.IsConnected;

        public void ClientOption(MqClientOptionItem optionItem) {
            mMqttClientOptions = new MqttClientOptionsBuilder()
                .WithClientId(optionItem.ClientId)
                .WithCleanSession(optionItem.CleanSession)
                .WithCredentials(optionItem.Username, optionItem.Password)
                .WithTcpServer(optionItem.Ip, optionItem.Port)
                .WithProtocolVersion(optionItem.ProtocolVersion)
                .Build();
        }

        public void SubscribeOption(params MqClientSubscribeItem[] items) {
            mMqClientSubscribeItems = items;
        }

        private async Task SubscribeAsync(params MqClientSubscribeItem[] items) {
            foreach (var item in items) {
                mMqttTopicFilters.Add(new MqttTopicFilter() {
                    Topic = item.Topic,
                    QualityOfServiceLevel = item.Level,
                    NoLocal = item.NoLocal,
                    RetainAsPublished = item.RetainAsPublished,
                });
            }

            mMqttSubscribeOptions.TopicFilters = mMqttTopicFilters;

            await mMqttClient.SubscribeAsync(mMqttSubscribeOptions);
        }

        public void StartAsync(int seconds = 10) {
            Task.Factory.StartNew(async () =>
            {
                while (true) {
                    try {
                        if (mMqttClient.IsConnected == false) {

                            mMqttClient.ApplicationMessageReceivedAsync += async e =>
                            {
                                await Task.Factory.StartNew(() =>
                                {
                                    mReceive?.Invoke(new MqReceiveCallbackItem() {
                                        Topic = e.ApplicationMessage.Topic,
                                        Payload = Encoding.UTF8.GetString(e.ApplicationMessage.Payload)
                                    });
                                });
                            };

                            await mMqttClient.ConnectAsync(mMqttClientOptions);

                            if (mMqttClient.IsConnected) {
                                mOnConnectSuccess?.Invoke();
                            }

                            // Subscribe to topics when session is clean etc.
                            await SubscribeAsync(mMqClientSubscribeItems);
                        }
                    } catch {
                        mOnConnectFailed?.Invoke();
                    } finally {
                        await Task.Delay(TimeSpan.FromSeconds(seconds));
                    }
                }
            });
        }

        public async Task PublishStringAsync(string topic, string payload) {
            if (mMqttClient.IsConnected) {
                await mMqttClient.PublishStringAsync(topic, payload);
            }
        }

        public async Task PublishStringAsync(string topic, string payload, MqPublishMessageItem item) {
            if (mMqttClient.IsConnected) {
                await mMqttClient.PublishStringAsync(topic, payload, item.Level, item.Retain);
            }
        }

        private Action<MqReceiveCallbackItem> mReceive;
        public void ReceiveCallback(Action<MqReceiveCallbackItem> reveive) {
            mReceive = reveive;
        }

        private Action mOnConnectSuccess;
        public void OnConnectSuccess(Action mCon) {
            mOnConnectSuccess = mCon;
        }

        private Action mOnConnectFailed;
        public void OnConnectFailed(Action mFailed) {
            mOnConnectFailed = mFailed;
        }
    }
}